package nl.unimaas.games.tron.player.mcts;

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;

import nl.unimaas.games.tron.engine.Board;
import nl.unimaas.games.tron.experiment.GameAnalyzer;
import nl.unimaas.games.tron.experiment.GameAnalyzer.MoveStatistics;
import nl.unimaas.games.tron.util.KnuthRandom;

@SuppressWarnings("serial")
public interface PlayoutStrategy extends Serializable {

    public int getMove(Board board, int x, int y, int playerNr);
    
    public final static PlayoutStrategy RANDOM = new PlayoutStrategy() {

        @Override
        public int getMove(Board board, int x, int y, int playerNr) {
            ArrayList<Integer> moves = board.getValidMovesFiltered(x, y);
            if (!moves.isEmpty()) {
                return moves.get(KnuthRandom.nextInt(moves.size()));
            } else {
                return Board.MOVE_NONE;
            }
        }

        @Override
        public String toString() {
            return "random";
        }
    };
    public final static PlayoutStrategy SUICIDAL_RANDOM = new PlayoutStrategy() {

        @Override
        public int getMove(Board board, int x, int y, int playerNr) {
            ArrayList<Integer> moves = board.getValidMoves(x, y);
            if (!moves.isEmpty()) {
                return moves.get(KnuthRandom.nextInt(moves.size()));
            } else {
                return Board.MOVE_NONE;
            }
        }

        @Override
        public String toString() {
            return "suicidal-random";
        }
    };
    public final static PlayoutStrategy WALLHUG = new PlayoutStrategy() {

        @Override
        public int getMove(Board board, int x, int y, int playerNr) {
            ArrayList<Integer> moves = new ArrayList<Integer>(3);
            if (x > 0 && board.isEmpty(x - 1, y) && board.getWallCount(x - 1, y) > 1 && !board.isSuicideMove(x, y, x - 1, y)) {
                moves.add(Board.MOVE_LEFT);
            }
            if (y > 0 && board.isEmpty(x, y - 1) && board.getWallCount(x, y - 1) > 1 && !board.isSuicideMove(x, y, x, y - 1)) {
                moves.add(Board.MOVE_UP);
            }
            if (x < board.getWidth() - 1 && board.isEmpty(x + 1, y) && board.getWallCount(x + 1, y) > 1 && !board.isSuicideMove(x, y, x + 1, y)) {
                moves.add(Board.MOVE_RIGHT);
            }
            if (y < board.getHeight() - 1 && board.isEmpty(x, y + 1) && board.getWallCount(x, y + 1) > 1 && !board.isSuicideMove(x, y, x, y + 1)) {
                moves.add(Board.MOVE_DOWN);
            }

            if (!moves.isEmpty()) {
                return moves.get(KnuthRandom.nextInt(moves.size()));
            } else {
                return RANDOM.getMove(board, x, y, playerNr);
            }
        }

        @Override
        public String toString() {
            return "wallhug";
        }
    };
    public final static PlayoutStrategy OFFENSIVE = new PlayoutStrategy() {

        @Override
        public int getMove(Board board, int x, int y, int playerNr) {
            //moves toward the enemy (no pathfinding)
            int pos = board.getPlayerPosition(1 - playerNr);
            int x2 = Board.posToX(pos), y2 = Board.posToY(pos);

            ArrayList<Integer> moves = new ArrayList<Integer>(3);
            if (x != x2) {
                if (x > x2 && board.isEmpty(x - 1, y)) {
                    moves.add(Board.MOVE_LEFT);
                } else if (x < x2 && board.isEmpty(x + 1, y)) {
                    moves.add(Board.MOVE_RIGHT);
                }
            }
            if (y != y2) {
                if (y > y2 && board.isEmpty(x, y - 1)) {
                    moves.add(Board.MOVE_UP);
                } else if (y < y2 && board.isEmpty(x, y + 1)) {
                    moves.add(Board.MOVE_DOWN);
                }
            }

            if (moves.isEmpty()) {
                return RANDOM.getMove(board, x, y, playerNr);
            } else {
                return moves.get(KnuthRandom.nextInt(moves.size()));
            }
        }

        @Override
        public String toString() {
            return "offensive";
        }
    };
    public final static PlayoutStrategy DEFENSIVE = new PlayoutStrategy() {

        @Override
        public int getMove(Board board, int x, int y, int playerNr) {
            //moves toward the enemy (no pathfinding)
            int pos = board.getPlayerPosition(1 - playerNr);
            int x2 = Board.posToX(pos), y2 = Board.posToY(pos);

            ArrayList<Integer> moves = new ArrayList<Integer>(3);
            if (x != x2) {
                if (x > x2 && board.isEmpty(x + 1, y)) {
                    moves.add(Board.MOVE_RIGHT);
                } else if (x < x2 && board.isEmpty(x - 1, y)) {
                    moves.add(Board.MOVE_LEFT);
                }
            }
            if (y != y2) {
                if (y > y2 && board.isEmpty(x, y + 1)) {
                    moves.add(Board.MOVE_DOWN);
                } else if (y < y2 && board.isEmpty(x, y - 1)) {
                    moves.add(Board.MOVE_UP);
                }
            }

            if (moves.isEmpty()) {
                return RANDOM.getMove(board, x, y, playerNr);
            } else {
                return moves.get(KnuthRandom.nextInt(moves.size()));
            }
        }

        @Override
        public String toString() {
            return "defensive";
        }
    };
    public final static PlayoutStrategy MIXED = new PlayoutStrategy() {

        private final PlayoutStrategy[] strategies = new PlayoutStrategy[]{RANDOM, WALLHUG, OFFENSIVE, DEFENSIVE};
        //normalized weights
        private final double[] weights = new double[]{0.2, 0.5, 0.050, 0.26};

        @Override
        public int getMove(Board board, int x, int y, int playerNr) {
            double cumprob = KnuthRandom.nextDouble();
            int i = -1;
            double sum = 0;
            do {
                sum += weights[++i];
            } while (sum < cumprob);
            return strategies[i].getMove(board, x, y, playerNr);
        }

        @Override
        public String toString() {
            return "mixed";
        }
    };
    public final static PlayoutStrategy CATEGORICAL = new PlayoutStrategy() {

        private final String FILE = "data" + File.separator + "global.dat";
        private MoveStatistics stats;

        {
            try {
                stats = MoveStatistics.fromFile(FILE);
            } catch (Exception e) {
                System.err.println("Could not load the PlayoutStrategy.Categorical stats file");
            }
        }

        @Override
        public int getMove(Board board, int x, int y, int playerNr) {
            ArrayList<Integer> moves = board.getValidMovesFiltered(x, y);
            if (moves.size() == 0) {
                return PlayoutStrategy.RANDOM.getMove(board, x, y, playerNr);
            }
            int cat;
            double[] probs = new double[moves.size()];
            double probsum = 0;
            for (int i = moves.size(); --i >= 0;) {
                cat = GameAnalyzer.getCategory(board, playerNr, moves.get(i));
                probs[i] = stats.getTransitionalProbability(cat);
                probsum += probs[i];
            }
            double cumprob = KnuthRandom.nextDouble() * probsum;

            int i = -1;
            double sum = 0;
            do {
                sum += probs[++i];
            } while (sum < cumprob);
            return moves.get(i);
        }

        @Override
        public String toString() {
            return "categorical";
        }
    };
    public final static PlayoutStrategy EGREEDY = new PlayoutStrategy() {

        private final PlayoutStrategy mainStrategy = WALLHUG;
        private final double mainProb = 0.9;
        private final PlayoutStrategy[] otherStrategies = new PlayoutStrategy[]{RANDOM, DEFENSIVE, CATEGORICAL, OFFENSIVE};

        @Override
        public int getMove(Board board, int x, int y, int playerNr) {
            double prob = KnuthRandom.nextDouble();
            int move;
            if (mainProb < prob) {
                move = mainStrategy.getMove(board, x, y, playerNr);
            } else {
                prob = KnuthRandom.nextDouble();
                move = otherStrategies[(int) (prob * otherStrategies.length)].getMove(board, x, y, playerNr);
            }
            int x2 = x, y2 = y;
            if (move < 0) {
                if (move == Board.MOVE_LEFT) {
                    x2--;
                } else {
                    y2--;
                }
            } else {
                if (move == Board.MOVE_RIGHT) {
                    x2++;
                } else {
                    y2++;
                }
            }
            if (board.isSuicideMove(x, y, x2, y2)) {
                return RANDOM.getMove(board, x, y, playerNr);
            } else {
                return move;
            }
        }

        @Override
        public String toString() {
            return "e-greedy";
        }
    };
}
